/***
* ==++==
*
* Copyright (c) Microsoft Corporation.  All rights reserved.
*
* ==--==
* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
*
* json.h
*
* HTTP Library: JSON parser and writer
*
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
****/
#pragma once

#include <hash_map>
#include <memory>
#include <string>
#include <sstream>
#include <vector>

#include "xxpublic.h"

namespace http { namespace json
{
    namespace details 
    {
        class _Value; 
        class _Number; 
        class _Null; 
        class _Boolean; 
        class _String; 
        class _Object; 
        class _Array; 
        class JSON_Parser;
    }

    /// <summary>
    /// A JSON value represented as a C++ class.
    /// </summary>
    class value
    {
    public:
        /// <summary>
        /// This enumeration represents the various kinds of JSON values.
        /// </summary>
        enum value_type { Number, Boolean, String, Object, Array, Null };

        typedef std::hash_map<std::string, json::value> field_map;
        typedef std::vector<json::value>                element_vector;

        /// <summary>
        /// Constructor creating a null value
        /// </summary>
        _ASYNCRTIMP value();

        /// <summary>
        /// Constructor creating a JSON number value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from</param>
        _ASYNCRTIMP value(int value);

        /// <summary>
        /// Constructor creating a JSON number value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from</param>
        _ASYNCRTIMP value(double value);

        /// <summary>
        /// Constructor creating a JSON Boolean value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from</param>
        _ASYNCRTIMP explicit value(bool value);

        /// <summary>
        /// Constructor creating a JSON string value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from, a null-terminated single-byte string</param>
        _ASYNCRTIMP explicit value(const char *value);

        /// <summary>
        /// Constructor creating a JSON string value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from, a null-terminated double-byte string</param>
        _ASYNCRTIMP explicit value(const wchar_t *);

        /// <summary>
        /// Constructor creating a JSON string value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from, a C++ STL single-byte string</param>
        _ASYNCRTIMP explicit value(const std::string &);

        /// <summary>
        /// Constructor creating a JSON string value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from, a C++ STL double-byte string</param>
        _ASYNCRTIMP explicit value(const std::wstring &);

        /// <summary>
        /// Copy constructor
        /// </summary>
        _ASYNCRTIMP value(const value &);

        /// <summary>
        /// Move constructor
        /// </summary>
        _ASYNCRTIMP value(value &&);

        /// <summary>
        /// Assignment operator.
        /// </summary>
        _ASYNCRTIMP value &operator=(const value &);

        /// <summary>
        /// Move assignment operator.
        /// </summary>
        _ASYNCRTIMP value &operator=(value &&);

        /// <summary>
        /// Constructor creating a JSON value from an input stream, by parsing its contents.
        /// </summary>
        /// <param name="input">The stream to read the JSON value from</param>
        _ASYNCRTIMP value(std::istream &input);

        /// <summary>
        /// Constructor creating a JSON value from an input stream, by parsing its contents.
        /// </summary>
        /// <param name="input">The stream to read the JSON value from</param>
        _ASYNCRTIMP value(std::wistream &input);


        // Static factories

        /// <summary>
        /// Create a null value
        /// </summary>
        /// <returns>A JSON null value</returns>
        static _ASYNCRTIMP value __cdecl null();

        /// <summary>
        /// Create a number value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from</param>
        /// <returns>A JSON number value</returns>
        static _ASYNCRTIMP value __cdecl number(double value);

        /// <summary>
        /// Create a Boolean value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from</param>
        /// <returns>A JSON Boolean value</returns>
        static _ASYNCRTIMP value __cdecl boolean(bool value);

        /// <summary>
        /// Create a string value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from</param>
        /// <returns>A JSON string value</returns>
        static _ASYNCRTIMP value __cdecl string(const std::string &value);

        /// <summary>
        /// Create a string value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from</param>
        /// <returns>A JSON string value</returns>
        static _ASYNCRTIMP value __cdecl string(const std::wstring &value);

#if defined(__cplusplus_winrt)
        /// <summary>
        /// Create a string value
        /// </summary>
        /// <param name="value">The C++ value to create a JSON value from, a WinRT string reference</param>
        /// <returns>A JSON string value</returns>
        static _ASYNCRTIMP json::value __cdecl string(Platform::String ^);
#endif

        /// <summary>
        /// Create an object value
        /// </summary>
        /// <returns>An empty JSON object value</returns>
        static _ASYNCRTIMP json::value __cdecl object();

        /// <summary>
        /// Create an object value from a map of field/values
        /// </summary>
        /// <param name="fields">A map of field names associated with JSON values</param>
        /// <returns>A non-empty JSON object value</returns>
        static _ASYNCRTIMP json::value __cdecl object(const json::value::field_map &fields);

        /// <summary>
        /// Create an empty JSON array
        /// </summary>
        /// <returns>An empty JSON array value</returns>
        static _ASYNCRTIMP json::value __cdecl array();

        /// <summary>
        /// Create a JSON array
        /// </summary>
        /// <param name="size">The initial number of elements of the JSON value</param>
        /// <returns>A JSON array value</returns>
        static _ASYNCRTIMP json::value __cdecl array(size_t size);

        /// <summary>
        /// Create a JSON array
        /// </summary>
        /// <param name="elements">A vector of JSON values, </param>
        /// <returns>A JSON array value</returns>
        static _ASYNCRTIMP json::value __cdecl array(const json::value::element_vector &elements);

        /// <summary>
        /// Access the type of JSON value the current value instance is
        /// </summary>
        /// <returns>The value's type</returns>
        _ASYNCRTIMP json::value::value_type __cdecl type() const;

        /// <summary>
        /// Access the fields of a JSON object value
        /// </summary>
        /// <returns>A reference to the map containing the fields of the object</returns>
        _ASYNCRTIMP const json::value::field_map & __cdecl fields() const;

        /// <summary>
        /// Access the elements of a JSON array value
        /// </summary>
        /// <returns>A reference to the map containing the elements of the array</returns>
        _ASYNCRTIMP const json::value::element_vector & __cdecl elements() const;


        /// <summary>
        /// Is the current value a null value?
        /// </summary>
        /// <returns>True if the value is a null value, false otherwise</returns>
        bool is_null() const { return type() == Null; };

        /// <summary>
        /// Is the current value a number value?
        /// </summary>
        /// <returns>True if the value is a number value, false otherwise</returns>
        bool is_number() const { return type() == Number; }

        /// <summary>
        /// Is the current value a Boolean value?
        /// </summary>
        /// <returns>True if the value is a Boolean value, false otherwise</returns>
        bool is_boolean() const { return type() == Boolean; }

        /// <summary>
        /// Is the current value a string value?
        /// </summary>
        /// <returns>True if the value is a string value, false otherwise</returns>
        bool is_string() const { return type() == String; }

        /// <summary>
        /// Is the current value an object?
        /// </summary>
        /// <returns>True if the value is an object, false otherwise</returns>
        bool is_array() const { return type() == Array; }

        /// <summary>
        /// Is the current value an array?
        /// </summary>
        /// <returns>True if the value is an array, false otherwise</returns>
        bool is_object() const { return type() == Object; }

        /// <summary>
        /// Represent the current JSON value as a single-byte string.
        /// </summary>
        /// <returns>A string representation of the value</returns>
        _ASYNCRTIMP std::string to_string() const;

        /// <summary>
        /// Represent the current JSON value as a double-byte string.
        /// </summary>
        /// <returns>A string representation of the value</returns>
        _ASYNCRTIMP std::wstring to_wstring() const;

        /// <summary>
        /// Write the current JSON value as a single-byte string to a stream instance.
        /// </summary>
        /// <param name="stream">The stream that the JSON string representation should be written to.</param>
        /// <returns>A string representation of the value</returns>
        _ASYNCRTIMP void to_string(std::ostream &stream) const;

        /// <summary>
        /// Write the current JSON value as a double-byte string to a stream instance.
        /// </summary>
        /// <param name="stream">The stream that the JSON string representation should be written to.</param>
        /// <returns>A string representation of the value</returns>
        _ASYNCRTIMP void to_wstring(std::wostream &stream) const;

        /// <summary>
        /// Convert the JSON value to a C++ double, if and only if it is a number value.
        /// </summary>
        /// <returns>A double representation of the value</returns>
        _ASYNCRTIMP double as_double() const;

        /// <summary>
        /// Convert the JSON value to a C++ bool, if and only if it is a Boolean value.
        /// </summary>
        /// <returns>A C++ bool representation of the value</returns>
        _ASYNCRTIMP bool as_bool() const;

        /// <summary>
        /// Convert the JSON value to a C++ STL string, if and only if it is a string value.
        /// </summary>
        /// <returns>A C++ STL string representation of the value</returns>
        _ASYNCRTIMP std::string as_string() const;

        /// <summary>
        /// Convert the JSON value to a C++ STL string, if and only if it is a string value.
        /// </summary>
        /// <returns>A C++ STL string representation of the value</returns>
        _ASYNCRTIMP std::wstring as_wstring() const;

        /// <summary>
        /// Access a field of a JSON object.
        /// </summary>
        /// <param name="key">The name of the field</param>
        /// <returns>A reference to the value kept in the field</returns>
        _ASYNCRTIMP value & operator [] (const std::string &key);

        /// <summary>
        /// Access a field of a JSON object.
        /// </summary>
        /// <param name="key">The name of the field</param>
        /// <returns>A reference to the value kept in the field</returns>
        _ASYNCRTIMP value & operator [] (const std::wstring &key);

        /// <summary>
        /// Access an element of a JSON array.
        /// </summary>
        /// <param name="key">The name of the field</param>
        /// <returns>A reference to the value kept in the field</returns>
        _ASYNCRTIMP value & operator [] (size_t index);
        
    private:
        friend class details::_Value;
        friend class details::_Number;
        friend class details::_Boolean;
        friend class details::_String;
        friend class details::_Object;
        friend class details::_Array;
        friend class details::JSON_Parser;

        value(details::_Value *v) : m_value(v) {}
        std::unique_ptr<details::_Value> m_value;
    };
    
    /// <summary>
    /// A single exception type to represent errors in parsing, converting, and accessing
    /// elements of JSON values.
    /// </summary>
    class json_exception : public std::exception
    { 
    public:  
        _ASYNCRTIMP json_exception() {}
        _ASYNCRTIMP json_exception(const char * const &message) : std::exception(message) { }
        _ASYNCRTIMP json_exception(const char * const &message, int code) : std::exception(message, code) { }
    };

    namespace details
    {
        class _Value 
        {
        public:
            virtual std::unique_ptr<_Value> _copy_value() = 0;


            virtual const json::value::element_vector &elements() const { throw json_exception("not an array"); }
            virtual const json::value::field_map &fields() const { throw json_exception("not an object"); }

            virtual value &index(const std::string &) { throw json_exception("not an object"); }
            virtual value &index(std::vector<value>::size_type) { throw json_exception("not an array"); }

            virtual std::string to_string() const { return "null"; }
            virtual void to_string(std::ostream &stream) const { stream << "null"; }
            virtual std::wstring to_wstring() const { return L"null"; }
            virtual void to_wstring(std::wostream &stream) const { stream << L"null"; }

            virtual json::value::value_type type() const { return json::value::Null; }

            virtual double as_double() const { throw json_exception("not a number"); }
            virtual bool as_bool() const { throw json_exception("not a boolean"); }
            virtual std::string as_string() const { throw json_exception("not a string"); }
            virtual std::wstring as_wstring() const { throw json_exception("not a string"); }

        private:
        };

        class _Null : public _Value
        {
        public:

            virtual std::unique_ptr<_Value> _copy_value()
            {
                return std::unique_ptr<_Value>(new _Null(*this));
            }

            virtual std::string to_string() const { return "null"; }
            virtual void to_string(std::ostream &stream) const { stream << "null"; }
            virtual std::wstring to_wstring() const { return L"null"; }
            virtual void to_wstring(std::wostream &stream) const { stream << L"null"; }

            virtual json::value::value_type type() const { return json::value::Null; }

        private:
            friend class json::value;

            _Null() { }
        };

        class _Number : public _Value
        {
        public:

            virtual std::unique_ptr<_Value> _copy_value()
            {
                return std::unique_ptr<_Value>(new _Number(*this));
            }

            virtual std::string to_string() const { std::ostringstream os; os << m_value; return os.str(); }
            virtual void to_string(std::ostream &stream) const { stream << m_value; }
            virtual std::wstring to_wstring() const { std::wostringstream os; os << m_value; return os.str(); }
            virtual void to_wstring(std::wostream &stream) const { stream << m_value; }

            virtual json::value::value_type type() const { return json::value::Number; }

            virtual double as_double() const { return m_value; }

        private:
            friend class json::value;

            _Number(double value) : m_value(value) { }

            double m_value;
        };

        class _Boolean : public _Value
        {
        public:

            virtual std::unique_ptr<_Value> _copy_value()
            {
                return std::unique_ptr<_Value>(new _Boolean(*this));
            }

            virtual std::string to_string() const { return m_value ? "true" : "false"; }
            virtual void to_string(std::ostream &stream) const { stream << (m_value ? "true" : "false"); }
            virtual std::wstring to_wstring() const { return m_value ? L"true" : L"false"; }
            virtual void to_wstring(std::wostream &stream) const { stream << (m_value ? L"true" : L"false"); }

            virtual json::value::value_type type() const { return json::value::Boolean; }

            virtual bool as_bool() const { return m_value; }

        private:
            friend class json::value;

            _Boolean(bool value) : m_value(value) { }

            bool m_value;
        };

        class _String : public _Value
        {
        public:

            virtual std::unique_ptr<_Value> _copy_value()
            {
                return std::unique_ptr<_Value>(new _String(*this));
            }

            virtual std::string to_string() const 
            {
                std::ostringstream stream;
                to_string(stream);
                return stream.str();
            }
            virtual void to_string(std::ostream &stream) const { stream << '"' << m_value << '"'; }
            virtual std::wstring to_wstring() const 
            { 
                std::wostringstream os; 
                to_wstring(os);
                return os.str(); 
            }
            virtual void to_wstring(std::wostream &stream) const { stream << L'"' << as_wstring() << L'"'; }

            virtual json::value::value_type type() const { return json::value::String; }

            virtual std::string as_string() const;
            virtual std::wstring as_wstring() const;

        private:
            friend class json::value;

            _String(std::string value) : m_value(value) { }

            std::string m_value;
        };

        class _Object : public _Value
        {
        public:

            virtual std::unique_ptr<_Value> _copy_value()
            {
                return std::unique_ptr<_Value>(new _Object(*this));
            }

            virtual std::string to_string() const { std::ostringstream os; to_string(os); return os.str(); }

            _ASYNCRTIMP virtual void to_string(std::ostream &os) const;
            virtual std::wstring to_wstring() const { std::wostringstream os; to_wstring(os); return os.str(); }
            _ASYNCRTIMP virtual void to_wstring(std::wostream &os) const;

            virtual json::value::value_type type() const { return json::value::Object; }

            virtual json::value &index(const std::string &key)
            {
                return m_fields[key];
            }

            virtual const json::value::field_map &fields() const { return m_fields; }

        private:
            friend class json::value;

            _Object() {}
            _Object(const json::value::field_map &fields) : m_fields(fields) { }

            json::value::field_map m_fields;
        };

        class _Array : public _Value
        {
        public:

            virtual std::unique_ptr<_Value> _copy_value()
            {
                return std::unique_ptr<_Value>(new _Array(*this));
            }

            virtual std::string to_string() const { std::ostringstream os; to_string(os); return os.str(); }

            _ASYNCRTIMP virtual void to_string(std::ostream &os) const;
            virtual std::wstring to_wstring() const { std::wostringstream os; to_wstring(os); return os.str(); }
            _ASYNCRTIMP virtual void to_wstring(std::wostream &os) const;

            virtual json::value::value_type type() const { return json::value::Array; }

            virtual json::value &index(std::vector<json::value>::size_type index)
            {
               size_t nMinSize = index + 1;
               size_t nlastSize = m_elements.size();
               if (nlastSize < nMinSize)
               {
                  m_elements.resize(nMinSize);
               }
               return m_elements[index]; 
            }

            virtual const json::value::element_vector &elements() const { return m_elements; }

        private:
            friend class json::value;

            _Array() {}
            _Array(std::vector<json::value>::size_type size) : m_elements(size) {}
            _Array(const json::value::element_vector &elements) : m_elements(elements) { }

            json::value::element_vector m_elements;
        };
    } // namespace details

    /// <summary>
    /// A standard std::ostream operator to facilitate writing JSON values to streams.
    /// </summary>
    _ASYNCRTIMP std::ostream& operator << (std::ostream &os, const json::value &val);

    /// <summary>
    /// A standard std::ostream operator to facilitate writing JSON values to streams.
    /// </summary>
    _ASYNCRTIMP std::wostream& operator << (std::wostream &os, const json::value &val);

    /// <summary>
    /// A standard std::ostream operator to facilitate reading JSON values from streams.
    /// </summary>
    _ASYNCRTIMP std::istream& operator >> (std::istream &is, json::value &val);

    /// <summary>
    /// A standard std::ostream operator to facilitate reading JSON values from streams.
    /// </summary>
    _ASYNCRTIMP std::wistream& operator >> (std::wistream &is, json::value &val);

}} // namespace http::json

